package clang.data;

import java.util.Vector;

import clang.control.CodeBlock;

public class Variable implements Expression {

	private String name;
	private String contextName;
	private boolean anonymus = true;
	
	//private CodeBlock parent = null;
	
	private double data = 0.0;
	private boolean initialized = false;
	private VarType myType = VarType.VOID;
	private int myAddress = -1;
	private boolean invalidated = false;
	
	private static Vector<Variable> memory = new Vector<Variable>();
	
	public Variable() { anonymus = true; initialized = true; myType = VarType.VOID; }
	
	public Variable(Variable other, String newName)
	{
		name = newName;
		anonymus = false;
		data = other.data;
		initialized = other.initialized;
		myType = other.myType;
		invalidated = false;
		if(initialized)
		{
			memory.add(this);
			myAddress = memory.indexOf(this);
		}
	}
	
	public static Variable generateArray(CodeBlock cb, String str, String name)
	{
		if(str.length() == 0) return new Variable(name + "[0]", VarType.CHAR, 0);
		
		char[] ca = str.toCharArray();
		
		Variable first = new Variable(name + "[0]", VarType.CHAR, ca[0]);
		cb.defineVariable(first);
		for(int i = 1; i < ca.length; ++i)
			cb.defineVariable(
					new Variable(name + "[" + i + "]", VarType.CHAR, ca[i]));
		
		cb.defineVariable(
				new Variable(name + "[" + ca.length + "]", VarType.CHAR, 0));
		
		try {
			Variable ref = new Variable(name, VarType.INT, first.getAddress());
			cb.defineVariable(ref);
			return ref;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
	
	public void erase()
	{
		invalidate();
		if(myAddress >= 0)
			memory.set(myAddress, null);
	}
	
	public Variable(int i)
	{
		this.name = "";
		anonymus = true;
		initialized = true;
		myType = VarType.INT;
		data = i;
	}
	
	public Variable(VarType type)
	{
		this.name = "";
		anonymus = true;
		initialized = false;
		myType = type;
	}
	
	public Variable(VarType type, double val)
	{
		this.name = "";
		anonymus = false;
		myType = type;
		try {
			setData(val);
		} catch (Exception e) {
			System.out.println("Error in constructor!");
		}
		anonymus = true;
		initialized = true;
	}
	
	public Variable(String name, VarType type)
	{
		this.name = name;
		anonymus = false;
		myType = type;
		initialized = false;
		memory.add(this);
		myAddress = memory.indexOf(this);
	}
	
	public Variable(String name, VarType type, double val)
	{
		this.name = name;
		anonymus = false;
		myType = type;
		data = val;
		initialized = true;
		memory.add(this);
		myAddress = memory.indexOf(this);
	}
	
	public Variable(VarType i, boolean b) {
		this.name = "";
		anonymus = false;
		myType = i;
		try {
			setData(b ? 1.0 : 0.0);
		} catch (Exception e) {
			System.out.println("Error in constructor!");
		}
		anonymus = true;
		initialized = true;
	}

	public Variable castTo(VarType newType)
	{
		Variable nvar = new Variable(newType);
		nvar.anonymus = true;
		if((myType == VarType.INT || myType == VarType.LONG || myType == VarType.CHAR) &&
				(newType == VarType.FLOAT || newType == VarType.DOUBLE))
		{
			System.out.println("Roundoff!");
			nvar.data = (int)data;
		}
		else
		{
			nvar.data = data;
		}
		nvar.initialized = true;
		nvar.anonymus = true;
		
		if(newType == VarType.CHAR)
		{
			if(nvar.data < 0 || nvar.data > 255)
			{
				System.out.println("Char overflow!");
				nvar.data = ((int) nvar.data) % 256;
			}
		}
		
		return nvar;
	}
	
	public double getData() throws Exception {
		if(myType == VarType.VOID)
		{
			throw new Exception("Void has no value!");
		}
		if(invalidated)
		{
			throw new Exception("Variable is out of scope!");
		}
		if(!initialized)
		{
			throw new Exception("Using uninitialized value! Var name : " + name); 
		}
		else
		{
			return data;
		}
	}
	
	public void setData(double val) throws Exception {
		if(anonymus) 
		{
			throw new Exception("Can't assign to a literal!");
		}
		if(invalidated)
		{
			throw new Exception("Variable is out of scope!");
		}
		if(myType == VarType.CHAR || myType == VarType.INT || myType == VarType.LONG)
		{
			data = (int)val;
		}
		
		if(myType == VarType.CHAR) {
			if(data < 0.0 || data > 255.0)
			{
				System.out.println("Char overflow!");
				data = (int)data % 256;
			}
		}
		initialized = true;
	}
	
	public void setData(Expression var) throws Exception {
		if(var.getType() == VarType.VOID)
		{
			throw new Exception("Can't assign void!");
		}
		else
		{
			if(var.getType() != myType)
			{
				setData(var.calculate().castTo(myType));
			}
			else
			{
				if(var instanceof Variable)
					setData(((Variable)var).getData());
				else
					setData(var.calculate());				
			}
		}
	}
	
	public boolean isInteger() {
		return myType == VarType.CHAR || myType == VarType.INT || myType == VarType.LONG;
	}
	
	public int getInteger() {
		return (int)data;
	}
	
	public boolean getBool() {
		return data != 0.0;
	}

	public String getName() {
		return name;
	}

	public String getContextName() {
		return contextName;
	}

	public boolean isAnonymus() {
		return anonymus;
	}

	public boolean isInitialized() {
		return initialized;
	}

	public VarType getType() {
		return myType;
	}
	
	public int getAddress() throws Exception {
		if(anonymus)
		{
			throw new Exception("Can't get address from literal!");
		}
		if(invalidated)
		{
			throw new Exception("Variable is out of scope!");
		}
		return myAddress;
	}
	
	public Variable getAddressVar() throws Exception {
		if(anonymus)
		{
			throw new Exception("Can't get address from literal!");
		}
		if(invalidated)
		{
			throw new Exception("Variable is out of scope!");
		}
		return new Variable(VarType.INT, myAddress);
	}
	
	public void invalidate() {
		invalidated = true;
	}
	
	public boolean isValid() {
		return !invalidated;
	}
	
	public static double getValueAt(int address) throws Exception {
		if(address < 0 || address >= memory.size())
		{
			throw new Exception("Invalid pointer - segfault");
		}
		return memory.get(address).getData();
	}
	
	public static Variable getValueAtVar(int address) throws Exception {
		if(address < 0 || address >= memory.size())
		{
			throw new Exception("Invalid pointer - segfault");
		}
		return memory.get(address);
	}
	
	public static VarType maxType(VarType t1, VarType t2)
	{
		if(t1.ordinal() > t2.ordinal())
			return t1;
		else
			return t2;
	}
	
	public String toString() {
		if(myType == VarType.CHAR)
			return myType + " " + name + " = " + data + " ('" + (char)data + "')";
		else
			return myType + " " + name + " = " + data;
	}

	@Override
	public Variable calculate() {
		return this;
	}

	@Override
	public void execute() {
	}
	
}
